package org.snake.spring.framework.webmvc.servlet;

import org.snake.mvcframework.annotation.RAYRequestParam;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

/**
 * 方法的参数适配器对象
 *
 * @author Ray
 */
public class RAYHandlerAdapter
{

    /**
     * 参数适配器的执行逻辑
     *
     * @param req     HttpServletRequest
     * @param resp    HttpServletResponse
     * @param handler RAYHandlerMapping
     * @return RAYModelAndView 调用方法后返回的ModelAndView
     * @throws Exception 抛出异常
     */
    public RAYModelAndView handle(HttpServletRequest req, HttpServletResponse resp, RAYHandlerMapping handler) throws Exception
    {

        // 1.保存参数的名称和位置
        Map<String, Integer> paramIndexMapping = new HashMap<>();

        // 2.从注解中拿取参数名字，此段逻辑主要处理的是使用RAYRequestParam注解的参数
        // 注意：此处是一个二维数组，即每一个形参上面都可以有多个注解
        Annotation[][] parameterAnnotations = handler.getMethod().getParameterAnnotations();
        // 循环取方法参数上的注解
        for (int i = 0; i < parameterAnnotations.length; i++)
        {

            for (Annotation annotation : parameterAnnotations[i])
            {
                if (annotation instanceof RAYRequestParam)
                {
                    // 注解中参数名称
                    String paramName = ((RAYRequestParam) annotation).value();
                    // 参数名称不为空
                    if (!"".equals(paramName.trim()))
                    {
                        // 保存参数的名称和位置
                        paramIndexMapping.put(paramName, i);
                    }
                }
            }
        }

        // 3. 拿到method的形参列表，此段逻辑主要处理的是HttpServletRequest和HttpServletResponse的参数位置
        Class<?>[] parameterTypes = handler.getMethod().getParameterTypes();
        // 循环取方法参数的类型
        for (int i = 0; i < parameterTypes.length; i++)
        {
            // 获取类信息
            Class<?> parameterType = parameterTypes[i];

            if (parameterType == HttpServletRequest.class || parameterType == HttpServletResponse.class)
            {
                paramIndexMapping.put(parameterType.getName(), i);
            }
        }

        // 4. 获取请求实际参数， j2ee规范中，一个key是可以对应多个值的。对应场景：多选框传值
        Map<String, String[]> params = req.getParameterMap();
        // 设置实参列表
        Object[] paramValues = new Object[parameterTypes.length];
        // 循环所有URL请求中的实参
        for (Map.Entry<String, String[]> param : params.entrySet())
        {
            // 如果形参列表里面，没有此参数名称，则直接跳过
            if(!paramIndexMapping.containsKey(param.getKey()))
            {
                continue;
            }
            // 实参的值，需要将此实参的值，设置到对应的形参位置上
            String value = Arrays.toString(param.getValue())
                    .replaceAll("\\[|\\]", "")      // 替换掉可能出现的两个方括号[]
                    .replaceAll("\\s", "");         // 替换掉转换可能出现的格式化的空格

            // 拿到此实参，在方法形参中的位置
            int index = paramIndexMapping.get(param.getKey());
            // 在实参列表的对应位置上赋值
            // 注意: 赋值需要做参数的类型转换
            paramValues[index] = caseStringValue(value, parameterTypes[index]);
        }

        // 给HttpServletRequest的参数赋值
        if(paramIndexMapping.containsKey(HttpServletRequest.class.getName()))
        {
            int index = paramIndexMapping.get(HttpServletRequest.class.getName());
            paramValues[index] = req;
        }

        // 给HttpServletResponse的参数赋值
        if(paramIndexMapping.containsKey(HttpServletResponse.class.getName()))
        {
            int index = paramIndexMapping.get(HttpServletResponse.class.getName());
            paramValues[index] = resp;
        }

        // 5. 使用反射机制，去调用方法
        Object invokeResult = handler.getMethod().invoke(handler.getController(), paramValues);

        if (null == invokeResult || invokeResult instanceof Void)
        {
            return null;
        }

        if (handler.getMethod().getReturnType() == RAYModelAndView.class)
        {
            return (RAYModelAndView) invokeResult;
        }

        return null;
    }

    /**
     * 参数的类型转换，将String类型转换为方法形参上对应的参数类型
     *
     * @param value 实际请求中，参数的值
     * @param parameterType 形参列表中对应位置的参数类型
     * @return Object 类型转换后的返回类型
     */
    private Object caseStringValue(String value, Class<?> parameterType)
    {

        if(String.class == parameterType)
        {
            return value;
        }

        if(Integer.class == parameterType)
        {
            return Integer.valueOf(value);
        }
        else if (Double.class == parameterType)
        {
            return Double.valueOf(value);
        }

        return value;
    }

}
